using System;
using System.Collections.Generic;
using System.Reflection;
using System.Threading;
using Core.Runtime;
using Cysharp.Threading.Tasks;
using UnityEngine;

namespace Hotfix.Runtime
{
    [DisallowMultipleComponent]
    public class UIManager : MonoBehaviour
    {
        #region Configuration
        [SerializeField] private Transform[] _layerContainers;
        [SerializeField] private float _stuckThreshold = 1f;
        [SerializeField] private bool _enableUpdate = true;
        [SerializeField] private bool _enableLateUpdate = true;
        #endregion

        #region Core Data

        private readonly Dictionary<UILayer, Transform> _layers = new();
        private readonly Dictionary<Type, UIBase> _activeInstances = new();

        #endregion

        #region Lifecycle

        private void Awake()
        {
            InitializeLayers();
            ValidateLayerContainers();
        }

        private void InitializeLayers()
        {
            foreach (UILayer type in Enum.GetValues(typeof(UILayer)))
            {
                if ((int)type >= _layerContainers.Length)
                {
                    throw new ArgumentException($"Layer container for {type} is not configured");
                }

                _layers[type] = _layerContainers[(int)type];
            }
        }

        private void ValidateLayerContainers()
        {
            if (_layerContainers == null || _layerContainers.Length != Enum.GetValues(typeof(UILayer)).Length)
            {
                throw new ArgumentException("Layer containers configuration mismatch");
            }
        }
        
        
        private void Update()
        {
            if (!_enableUpdate) return;

            foreach (var instance in _activeInstances.Values)
            {
                if (instance.gameObject.activeSelf && instance.enabled)
                {
                    instance.OnUpdate();
                }
            }
        }

        private void LateUpdate()
        {
            if (!_enableLateUpdate) return;

            foreach (var instance in _activeInstances.Values)
            {
                if (instance.gameObject.activeSelf && instance.enabled)
                {
                    instance.OnLateUpdate();
                }
            }
        }

        #endregion

        #region Public API

        public async UniTask<T> Show<T>(CancellationToken externalCt = default) where T : UIBase
        {
            using var linkedCts = CancellationTokenSource.CreateLinkedTokenSource(
                externalCt,
                this.GetCancellationTokenOnDestroy()
            );

            using var stuckTimer = new StuckTimer(_stuckThreshold);

            try
            {
                var (type, dataAttr) = ValidateUIComponents<T>();
                if (TryGetExistingUI(type, out var existing)) return (T)existing;

                var instance = await CreateUIInstance<T>(dataAttr, linkedCts.Token);
                await InitializeUI(instance, linkedCts.Token);
                RegisterActiveInstance(instance);

                return instance;
            }
            catch (OperationCanceledException)
            {
                Debug.LogWarning($"[UI] Show {typeof(T).Name} operation cancelled");
                throw;
            }
        }

        public async UniTask Hide(Type type, CancellationToken externalCt = default)
        {
            if (!_activeInstances.TryGetValue(type, out var instance)) return;
            using var linkedCts =
                CancellationTokenSource.CreateLinkedTokenSource(externalCt, this.GetCancellationTokenOnDestroy());
            try
            {
                await ExecuteCloseSequence(instance, linkedCts.Token);
            }
            finally
            {
                await CleanupResources(instance, type);
            }
        }

        public async UniTask Hide<T>(CancellationToken externalCt = default) where T : UIBase
        {
            var type = typeof(T);

            await Hide(type, externalCt);
        }

        #endregion

        #region Core Implementation

        private (Type type, UIDataAttribute dataAttr) ValidateUIComponents<T>() where T : UIBase
        {
            var type = typeof(T);
            var dataAttr = type.GetCustomAttribute<UIDataAttribute>()
                           ?? throw new MissingComponentException($"[UI] Missing UIDataAttribute on {type.Name}");

            if (string.IsNullOrEmpty(dataAttr.AssetPath))
                throw new ArgumentException($"[UI] Empty AssetPath in {type.Name}");

            return (type, dataAttr);
        }

        private bool TryGetExistingUI(Type type, out UIBase existing)
        {
            if (_activeInstances.TryGetValue(type, out existing))
            {
                existing.transform.SetAsLastSibling();
                return true;
            }

            return false;
        }

        private async UniTask<T> CreateUIInstance<T>(UIDataAttribute dataAttr, CancellationToken ct) where T : UIBase
        {
            var instance = await AssetManager.InstantiateAsync(dataAttr.AssetPath).AttachExternalCancellation(ct);
            instance.transform.SetParent(GetLayerContainer(dataAttr.Layer), false);
            return instance.GetComponent<T>();
        }

        private async UniTask InitializeUI<T>(T instance, CancellationToken ct) where T : UIBase
        {
            try
            {
                await instance.OnInitialize().AttachExternalCancellation(ct);
                await instance.OnPreShow().AttachExternalCancellation(ct);
                instance.gameObject.SetActive(true);
                await instance.OnShow().AttachExternalCancellation(ct);
            }
            catch
            {
                Destroy(instance.gameObject);
                throw;
            }
        }

        private void RegisterActiveInstance(UIBase instance)
        {
            var type = instance.GetType();
            if (_activeInstances.ContainsKey(type))
            {
                Debug.LogWarning($"[UI] Replacing existing UI instance of {type.Name}");
            }

            _activeInstances[type] = instance;
        }

        private async UniTask ExecuteCloseSequence(UIBase instance, CancellationToken ct)
        {
            await instance.OnPreHide().AttachExternalCancellation(ct);
            instance.gameObject.SetActive(false);
            await instance.OnHide().AttachExternalCancellation(ct);
        }

        private async UniTask CleanupResources(UIBase instance, Type type)
        {
            if (instance.AutoDestroy)
            {
                if (instance != null && instance.gameObject != null)
                {
                    await instance.OnDispose();
                    UnityEngine.Object.Destroy(instance.gameObject);
                }

                _activeInstances.Remove(type);
            }
        }

        #endregion

        #region Utilities

        private Transform GetLayerContainer(UILayer type)
        {
            return _layers.TryGetValue(type, out var container)
                ? container
                : throw new KeyNotFoundException($"[UI] Container not found for {type}");
        }

        #endregion

        #region Stuck Detection

        private class StuckTimer : IDisposable
        {
            private readonly CancellationTokenSource _cts = new();
            private readonly float _threshold;

            public StuckTimer(float threshold)
            {
                _threshold = threshold;
                if (threshold > 0)
                {
                    StartMonitoring();
                }
            }

            private void StartMonitoring()
            {
                UniTask.Delay(TimeSpan.FromSeconds(_threshold), cancellationToken: _cts.Token)
                    .ContinueWith(() => Debug.LogWarning("[UI] Operation took longer than expected"))
                    .Forget();
            }

            public void Dispose()
            {
                _cts.Cancel();
                _cts.Dispose();
            }
        }

        #endregion
    }
}